<html>
 <head>
  <meta charset="UTF-8">
 </head>
 <body>
  <p data-lake-id="uf73bc841" id="uf73bc841"><span data-lake-id="uc2c28af4" id="uc2c28af4">对于数据库的默认隔离级别，</span><strong><span data-lake-id="u04f0b9bc" id="u04f0b9bc">Oracle默认的隔离级别是 RC，而MySQL默认的隔离级别是 RR。</span></strong></p>
  <p data-lake-id="u89a627f3" id="u89a627f3"><br></p>
  <p data-lake-id="ue72e9ac6" id="ue72e9ac6"><span data-lake-id="u0a9868d0" id="u0a9868d0">那么，你知道为什么Oracle选择RC作为默认级别，而MySQL要选择RR作为默认的隔离级别吗？</span></p>
  <p data-lake-id="u34f5d491" id="u34f5d491"><br></p>
  <h3 data-lake-id="685f1dee" id="685f1dee"><span data-lake-id="ue9b7ec22" id="ue9b7ec22">Oracle 的隔离级别</span></h3>
  <p data-lake-id="ua25044d2" id="ua25044d2"><br></p>
  <p data-lake-id="u21e83435" id="u21e83435"><span data-lake-id="u44dddb5e" id="u44dddb5e">前面我们说过，Oracle只支持ANSI/ISO SQL定义的Serializable和Read Committed，其实，根据Oracle官方文档给出的介绍，Oracle支持三种隔离级别：</span></p>
  <p data-lake-id="ub6706629" id="ub6706629"><br></p>
  <p data-lake-id="u7bc10b9c" id="u7bc10b9c"><img src="https://cdn.nlark.com/yuque/0/2022/jpeg/5378072/1671949151582-88e8b900-d35b-449d-b205-9b0770689cce.jpeg?x-oss-process=image%2Fwatermark%2Ctype_d3F5LW1pY3JvaGVp%2Csize_68%2Ctext_SmF2YSA4IEd1IFA%3D%2Ccolor_FFFFFF%2Cshadow_50%2Ct_80%2Cg_se%2Cx_10%2Cy_10"></p>
  <p data-lake-id="u8b9066af" id="u8b9066af"><br></p>
  <p data-lake-id="ub911b7ac" id="ub911b7ac"><span data-lake-id="ud52447d1" id="ud52447d1">即Oracle支持Read Committed、Serializable和Read-Only。</span></p>
  <p data-lake-id="u701ed7fc" id="u701ed7fc"><br></p>
  <p data-lake-id="u0a011be3" id="u0a011be3"><span data-lake-id="u1a1b4f58" id="u1a1b4f58">Read-Only只读隔离级别类似于Serializable隔离级别，但是只读事务不允许在事务中修改数据，除非用户是SYS。</span></p>
  <p data-lake-id="u912223ef" id="u912223ef"><br></p>
  <p data-lake-id="u37fcfe35" id="u37fcfe35"><span data-lake-id="u44a8d729" id="u44a8d729">在Oracle这三种隔离级别中，Serializable和Read-Only显然都是不适合作为默认隔离级别的，那么就只剩Read Committed这个唯一的选择了。</span></p>
  <p data-lake-id="uf72d1fa0" id="uf72d1fa0"><br></p>
  <h3 data-lake-id="02e6c4cf" id="02e6c4cf"><span data-lake-id="u453b1a0c" id="u453b1a0c">MySQL 的隔离级别</span></h3>
  <p data-lake-id="u54c68e64" id="u54c68e64"><br></p>
  <p data-lake-id="u62fef63f" id="u62fef63f"><span data-lake-id="u46fddc59" id="u46fddc59">相比于Oracle，MySQL的默认隔离级别的可选范围就比较大了。</span></p>
  <p data-lake-id="u793f6752" id="u793f6752"><br></p>
  <p data-lake-id="u7e93733a" id="u7e93733a"><span data-lake-id="uccbe0f07" id="uccbe0f07">首先，我们先从四种隔离级别中排除Serializable和Read Uncommitted这两种，主要是因为这两个级别一个隔离级别太高，一个太低。太高的就会影响并发度，太低的就有脏读现象。</span></p>
  <p data-lake-id="ue0be3097" id="ue0be3097"><br></p>
  <p data-lake-id="uea8ba025" id="uea8ba025"><span data-lake-id="u0ae2e3cf" id="u0ae2e3cf">那么，剩下的RR和RC两种，怎么选？</span></p>
  <p data-lake-id="u03d484df" id="u03d484df"><br></p>
  <p data-lake-id="u820b606b" id="u820b606b"><span data-lake-id="u4173b69a" id="u4173b69a">在MySQL设计之初，他的定位就是提供一个稳定的关系型数据库。而为了要解决MySQL单点故障带来的问题，MySQL采用主从复制的机制。</span></p>
  <p data-lake-id="uae919067" id="uae919067"><br></p>
  <p data-lake-id="uc629d175" id="uc629d175"><strong><span data-lake-id="uf56376a5" id="uf56376a5">所谓主从复制，其实就是通过搭建MySQL集群，整体对外提供服务，集群中的机器分为主服务器（Master）和从服务器（Slave），主服务器提供写服务，从服务器提供读服务。</span></strong></p>
  <p data-lake-id="u8166a9e9" id="u8166a9e9"><br></p>
  <p data-lake-id="u0801c4b0" id="u0801c4b0"><span data-lake-id="u1ee20f3f" id="u1ee20f3f">为了保证主从服务器之间的数据的一致性，就需要进行</span><strong><span data-lake-id="ue3bad54e" id="ue3bad54e">数据同步</span></strong><span data-lake-id="uf22b1297" id="uf22b1297">，大致的同步过程如下，这里就不详细介绍了</span></p>
  <p data-lake-id="u80191a77" id="u80191a77"><br></p>
  <p data-lake-id="u8e0d2909" id="u8e0d2909"><img src="https://cdn.nlark.com/yuque/0/2022/jpeg/5378072/1671949151594-045df65d-f16e-41d9-8421-5c2779f49ce0.jpeg?x-oss-process=image%2Fwatermark%2Ctype_d3F5LW1pY3JvaGVp%2Csize_48%2Ctext_SmF2YSA4IEd1IFA%3D%2Ccolor_FFFFFF%2Cshadow_50%2Ct_80%2Cg_se%2Cx_10%2Cy_10"></p>
  <p data-lake-id="ufb6b8c52" id="ufb6b8c52"><br></p>
  <p data-lake-id="uc3e248c3" id="uc3e248c3"><strong><span data-lake-id="u6510934a" id="u6510934a">MySQL在主从复制的过程中，数据的同步是通过bin log进行的</span></strong><span data-lake-id="ueed090c6" id="ueed090c6">，简单理解就是主服务器把数据变更记录到bin log中，然后再把bin log同步传输给从服务器，从服务器接收到bin log之后，再把其中的数据恢复到自己的数据库存储中。</span></p>
  <p data-lake-id="u3cc20ed8" id="u3cc20ed8"><br></p>
  <p data-lake-id="u8b8f2db1" id="u8b8f2db1"><span data-lake-id="ufb4465e7" id="ufb4465e7">那么，binlog里面记录的是什么内容呢？格式是怎样的呢？</span></p>
  <p data-lake-id="u7552e919" id="u7552e919"><br></p>
  <p data-lake-id="ue298c0f4" id="ue298c0f4"><span data-lake-id="u0832623d" id="u0832623d">MySQL的bin log主要支持三种格式，分别是statement、row以及mixed。MySQL是在5.1.5版本开始支持row的、在5.1.8版本中开始支持mixed。</span></p>
  <p data-lake-id="u27d38ed3" id="u27d38ed3"><span data-lake-id="u273ee92f" id="u273ee92f">​</span><br></p>
  <p data-lake-id="ue134e954" id="ue134e954"><br></p>
  <p data-lake-id="uae43682f" id="uae43682f"><span data-lake-id="u40c49ec6" id="u40c49ec6">statement和row最大的区别，</span><strong><span data-lake-id="ua2dd4189" id="ua2dd4189">当binlog的格式为statement时，binlog 里面记录的就是 SQL 语句的原文</span></strong><span data-lake-id="ubf1e5166" id="ubf1e5166">（这句话很重要！！！后面会用的到）。</span></p>
  <p data-lake-id="u8980a4eb" id="u8980a4eb"><br></p>
  <p data-lake-id="u64524466" id="u64524466"><span data-lake-id="u0b0a125b" id="u0b0a125b">因为MySQL早期只有statement这种bin log格式，这时候，如果使用提交读(Read Committed)、未提交读(Read Uncommitted)这两种隔离级别会出现问题。</span></p>
  <p data-lake-id="u1f624591" id="u1f624591"><br></p>
  <p data-lake-id="uced68625" id="uced68625"><span data-lake-id="ub09f2540" id="ub09f2540">举个例子，有一个数据库表t1，表中有如下两条记录：</span></p>
  <p data-lake-id="u5d9dac88" id="u5d9dac88"><br></p>
  <pre lang="java"><code>
CREATE TABLE `t1` (
  `a` int(11) DEFAULT NULL,
  `b` int(11) DEFAULT NULL,
  KEY `b` (`b`)
) ENGINE=InnoDB DEFAULT CHARSET=latin1;

insert into t1 values(10,1);
</code></pre>
  <p data-lake-id="uede6beb9" id="uede6beb9"><br></p>
  <p data-lake-id="u80a5a5be" id="u80a5a5be"><span data-lake-id="u382d9a26" id="u382d9a26">接着开始执行两个事务的写操作：</span></p>
  <p data-lake-id="u95925773" id="u95925773"><br></p>
  <table data-lake-id="qSXmx" id="qSXmx" margin="true" width-mode="contain" class="lake-table" style="width: 745px">
   <colgroup>
    <col width="371">
    <col width="374">
   </colgroup>
   <tbody>
    <tr data-lake-id="ufe24a486" id="ufe24a486">
     <td data-lake-id="u286255bf" id="u286255bf">
      <p data-lake-id="u8276c7e3" id="u8276c7e3"><strong><span data-lake-id="ubb8225da" id="ubb8225da">Session 1</span></strong></p></td>
     <td data-lake-id="ucac983a1" id="ucac983a1">
      <p data-lake-id="ue9f89df8" id="ue9f89df8"><strong><span data-lake-id="ua4c54deb" id="ua4c54deb">Session 2</span></strong></p></td>
    </tr>
    <tr data-lake-id="uf60da9a2" id="uf60da9a2">
     <td data-lake-id="udfe3cda1" id="udfe3cda1">
      <p data-lake-id="ub4c3f036" id="ub4c3f036"><span data-lake-id="u11a21d75" id="u11a21d75">set session transaction isolation level read committed;</span></p></td>
     <td data-lake-id="u30901f76" id="u30901f76">
      <p data-lake-id="udb6d90c8" id="udb6d90c8"><span data-lake-id="u3aff2950" id="u3aff2950">​</span><br></p></td>
    </tr>
    <tr data-lake-id="uefa00899" id="uefa00899">
     <td data-lake-id="u26be832b" id="u26be832b">
      <p data-lake-id="u86d68511" id="u86d68511"><span data-lake-id="u8f830290" id="u8f830290">set autocommit = 0;</span></p></td>
     <td data-lake-id="u2203d2ea" id="u2203d2ea">
      <p data-lake-id="u65cfd51a" id="u65cfd51a"><span data-lake-id="u044a0abe" id="u044a0abe">set session transaction isolation level read committed;</span></p></td>
    </tr>
    <tr data-lake-id="u12e746ea" id="u12e746ea">
     <td data-lake-id="ucc5d5609" id="ucc5d5609">
      <p data-lake-id="u51b789f4" id="u51b789f4"><span data-lake-id="ucf92c976" id="ucf92c976">​</span><br></p></td>
     <td data-lake-id="u09e892e2" id="u09e892e2">
      <p data-lake-id="ua613351c" id="ua613351c"><span data-lake-id="u3fe95b01" id="u3fe95b01">set autocommit = 0;</span></p></td>
    </tr>
    <tr data-lake-id="u0ab0bc40" id="u0ab0bc40">
     <td data-lake-id="u705815da" id="u705815da">
      <p data-lake-id="u74c86a62" id="u74c86a62"><span data-lake-id="u9f71d478" id="u9f71d478">begin;</span></p></td>
     <td data-lake-id="u8fffcb5c" id="u8fffcb5c">
      <p data-lake-id="ub44844e6" id="ub44844e6"><span data-lake-id="u3bb0249c" id="u3bb0249c">begin;</span></p></td>
    </tr>
    <tr data-lake-id="u5e942b9c" id="u5e942b9c">
     <td data-lake-id="ue2873474" id="ue2873474">
      <p data-lake-id="u647a3169" id="u647a3169"><span data-lake-id="uca7b0805" id="uca7b0805">delete from t1 where b &lt; 100;</span></p></td>
     <td data-lake-id="u44762101" id="u44762101"></td>
    </tr>
    <tr data-lake-id="u61e0e4df" id="u61e0e4df">
     <td data-lake-id="u90656778" id="u90656778"></td>
     <td data-lake-id="u2d5953e4" id="u2d5953e4">
      <p data-lake-id="u6b8315c4" id="u6b8315c4"><span data-lake-id="uc752a917" id="uc752a917">insert into t1 values(10,99);</span></p></td>
    </tr>
    <tr data-lake-id="u420b90b7" id="u420b90b7">
     <td data-lake-id="u63db2290" id="u63db2290"></td>
     <td data-lake-id="u2937a1af" id="u2937a1af">
      <p data-lake-id="u2f1dee88" id="u2f1dee88"><span data-lake-id="u33fa3064" id="u33fa3064">commit;</span></p></td>
    </tr>
    <tr data-lake-id="u1634930d" id="u1634930d">
     <td data-lake-id="u0eaed899" id="u0eaed899">
      <p data-lake-id="u24e6b4dd" id="u24e6b4dd"><span data-lake-id="ue1d7dfa6" id="ue1d7dfa6">commit;</span></p></td>
     <td data-lake-id="ub78835dc" id="ub78835dc"></td>
    </tr>
   </tbody>
  </table>
  <p data-lake-id="u6e25669e" id="u6e25669e"><br></p>
  <p data-lake-id="u9542245d" id="u9542245d"><span data-lake-id="u4364025c" id="u4364025c">以上两个事务执行之后，数据库里面的记录会只有一条记录（10,99），这个发生在主库的数据变更大家都能理解。</span></p>
  <p data-lake-id="u7f6c38ad" id="u7f6c38ad"><span data-lake-id="ud6c45a4d" id="ud6c45a4d">​</span><br></p>
  <blockquote data-lake-id="u7ba10968" id="u7ba10968">
   <p data-lake-id="ua599439a" id="ua599439a"><span data-lake-id="ucc00902f" id="ucc00902f">即使 Session 1 的删除操作在 Session 2 的插入操作之后提交，由于 READ COMMITTED 的隔离级别，Session 2 的插入操作不会看到 Session 1 的删除操作，所以最后数据库中仍然会留下 Session 2 插入的记录 (10,99)。</span></p>
  </blockquote>
  <p data-lake-id="u7d026e9b" id="u7d026e9b"><br></p>
  <p data-lake-id="u3b8031b6" id="u3b8031b6"><span data-lake-id="u716e9ccf" id="u716e9ccf">以上两个事务执行之后，会在bin log中记录两条记录，因为事务2先提交，所以</span><code data-lake-id="u4c20a5b6" id="u4c20a5b6"><span data-lake-id="u3008566f" id="u3008566f">insert into t1 values(10,99);</span></code><span data-lake-id="u435a2f4b" id="u435a2f4b">会被优先记录，然后再记录</span><code data-lake-id="ub486b25d" id="ub486b25d"><span data-lake-id="uad5cc274" id="uad5cc274">delete from t1 where b &lt; 100;</span></code><span data-lake-id="u7d44ac43" id="u7d44ac43">（再次提醒：statement格式的bin log记录的是SQL语句的原文）</span></p>
  <p data-lake-id="u82afad0a" id="u82afad0a"><br></p>
  <p data-lake-id="u743d47ea" id="u743d47ea"><span data-lake-id="u43a86b08" id="u43a86b08">这样bin log同步到备库之后，SQL语句回放时，会先执行</span><code data-lake-id="u70d678fc" id="u70d678fc"><span data-lake-id="u46cd79b4" id="u46cd79b4">insert into t1 values(10,99);</span></code><span data-lake-id="u48ae4553" id="u48ae4553">，再执行</span><code data-lake-id="ud9024590" id="ud9024590"><span data-lake-id="u05c17d2a" id="u05c17d2a">delete from t1 where b &lt; 100;</span></code><span data-lake-id="uf8d0c712" id="uf8d0c712">。</span></p>
  <p data-lake-id="u19cfa834" id="u19cfa834"><br></p>
  <p data-lake-id="u633335fa" id="u633335fa"><span data-lake-id="uc215dd78" id="uc215dd78">这时候，数据库中的数据就会变成 EMPTY SET，即没有任何数据。这就导致主库和备库的数据不一致了！！！</span></p>
  <p data-lake-id="u2e9a9093" id="u2e9a9093"><br></p>
  <p data-lake-id="u45bcacab" id="u45bcacab"><span data-lake-id="u314fa03f" id="u314fa03f">为了避免这样的问题发生。MySQL就把数据库的默认隔离级别设置成了Repetable Read，那么，Repetable Read的隔离级别下是如何解决这样问题的那？</span></p>
  <p data-lake-id="u69d87873" id="u69d87873"><br></p>
  <p data-lake-id="u47ed3f6b" id="u47ed3f6b"><span data-lake-id="u22d790c2" id="u22d790c2">那是因为Repetable Read这种隔离级别，会在更新数据的时候不仅对更新的行加行级锁，还会增加</span><strong><span data-lake-id="uda7643c2" id="uda7643c2">GAP锁和临键锁</span></strong><span data-lake-id="ub44609ce" id="ub44609ce">。上面的例子，在事务2执行的时候，因为事务1增加了</span><strong><span data-lake-id="u2b95ec5a" id="u2b95ec5a">GAP锁和临键锁</span></strong><span data-lake-id="u00ed2cd3" id="u00ed2cd3">，就会导致事务2执行被卡住，需要等事务1提交或者回滚后才能继续执行。</span></p>
  <p data-lake-id="u171f5228" id="u171f5228"><br></p>
  <p data-lake-id="u618c5cab" id="u618c5cab"><span data-lake-id="uc4cc1965" id="uc4cc1965">除了设置默认的隔离级别外，MySQL还禁止在使用statement格式的bin log的情况下，使用READ COMMITTED作为事务隔离级别。</span></p>
  <p data-lake-id="ue0cdb4b1" id="ue0cdb4b1"><br></p>
  <p data-lake-id="u54972d0c" id="u54972d0c"><span data-lake-id="u1dde35c2" id="u1dde35c2">一旦用户主动修改隔离级别，尝试更新时，会报错：</span></p>
  <p data-lake-id="u47a6119a" id="u47a6119a"><br></p>
  <pre lang="java"><code>
ERROR 1598 (HY000): Binary logging not possible. Message: Transaction level 'READ-COMMITTED' in InnoDB is not safe for binlog mode 'STATEMENT'
</code></pre>
  <p data-lake-id="u9878a9d2" id="u9878a9d2"><br></p>
  <p data-lake-id="u0be8fbb4" id="u0be8fbb4"><br></p>
  <p data-lake-id="u5dd0df3b" id="u5dd0df3b"><span data-lake-id="u1c4ff8ef" id="u1c4ff8ef">所以，现在我们知道了，为什么MySQL选择RR作为默认的数据库隔离级别了吧，其实就是为了兼容历史上的那种statement格式的bin log。</span></p>
  <p data-lake-id="u25d0cf83" id="u25d0cf83"><span data-lake-id="ubdc9a59a" id="ubdc9a59a">​</span><br></p>
  <h2 data-lake-id="et2w4" id="et2w4"><span data-lake-id="ube433c41" id="ube433c41">为什么很多大厂会把默认的RR改成RC</span></h2>
  <p data-lake-id="u25862125" id="u25862125"><br></p>
 </body>
</html>